#region Using directives
using System;
using System.Collections;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RacingGame;
using System.Collections.Generic;
using RacingGame.Helpers;
using RacingGame.Shaders;
#endregion

namespace RacingGame.Graphics
{
	internal class LineManager3D : IDisposable
	{
		#region Line struct
		struct Line
		{
			public Vector3 startPoint, endPoint;
			public Color startColor, endColor;
			
			public Line
			(Vector3 setStartPoint, Color setStartColor,
			 Vector3 setEndPoint, Color setEndColor)
			{
				startPoint = setStartPoint;
				startColor = setStartColor;
				endPoint = setEndPoint;
				endColor = setEndColor;
			}
			
			public static bool operator==(Line a, Line b)
			{
				return
					a.startPoint == b.startPoint &&
					a.endPoint == b.endPoint &&
					a.startColor == b.startColor &&
					a.endColor == b.endColor;
			}
			
			public static bool operator != (Line a, Line b)
			{
				return
					a.startPoint != b.startPoint ||
					a.endPoint != b.endPoint ||
					a.startColor != b.startColor ||
					a.endColor != b.endColor;
			}
			
			public override bool Equals(object a)
			{
				if (a is Line)
					return (Line)a == this;
					
					return false;
			}
			
			public override int GetHashCode()
			{
				return 0;
			}
		}
		#endregion
		
		#region Variables
		private int numOfLines = 0;
		
		private List<Line> lines = new List<Line>();
		
		private bool buildVertexBuffer = false;

        VertexPositionColor[] lineVertices = 
			new VertexPositionColor[MaxNumOfLines * 2];
			
		private int numOfPrimitives = 0;
		
		protected const int MaxNumOfLines = 
			4096;
			
		VertexDeclaration decl = null;
		#endregion

        #region Initialization
        public LineManager3D()
        {
            if (BaseGame.Device == null)
                throw new ArgumentNullException(
                    "XNA device is not initialized. can't init line manager.");

            decl = new VertexDeclaration(
                BaseGame.Device, VertexPositionColor.VertexElements);
        }
        #endregion

        #region Dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                decl.Dispose();
            }
        }
        #endregion

        #region AddLine
        public void AddLine(
            Vector3 startPoint, Color startColor,
            Vector3 endPoint, Color endColor)
        {
            if (numOfLines >= MaxNumOfLines)
            {
                return;
            }

            Line line = new Line(startPoint, startColor, endPoint, endColor);

            if (lines.Count > numOfLines)
            {
                if ((Line)lines[numOfLines] != line)
                {
                    lines[numOfLines] = line;
                    buildVertexBuffer = true;
                }
            }
            else
            {
                lines.Add(line);
                buildVertexBuffer = true;
            }

            numOfLines++;
        }

        public void AddLine(Vector3 startPoint, Vector3 endPoint,
            Color color)
        {
            AddLine(startPoint, color, endPoint, color);
        }
        #endregion

        #region Update vertex buffer
        protected void UpdateVertexBuffer()
        {
            if (numOfLines == 0 ||
                lines.Count < numOfLines)
            {
                numOfPrimitives = 0;
                return;
            }

            for (int lineNum = 0; lineNum < numOfLines; lineNum++)
            {
                Line line = (Line)lines[lineNum];
                lineVertices[lineNum * 2 + 0] = new VertexPositionColor(
                    line.startPoint, line.startColor);
                lineVertices[lineNum * 2 + 1] = new VertexPositionColor(
                    line.endPoint, line.endColor);
            }
            numOfPrimitives = numOfLines;

            buildVertexBuffer = false;
        }
        #endregion

        #region Render
        public void Render()
        {
            if (buildVertexBuffer ||
                   numOfPrimitives != numOfLines)
            {
                UpdateVertexBuffer();
            }

            if (numOfPrimitives > 0)
            {
                BaseGame.WorldMatrix = Matrix.Identity;
                ShaderEffect.lineRendering.Render(
                    "LineRendering3D",
                    delegate
                    {
                        BaseGame.SetAlphaBlendingEnabled(true);
                        BaseGame.Device.VertexDeclaration = decl;
                        BaseGame.Device.DrawUserPrimitives<VertexPositionColor>(
                            PrimitiveType.LineList, lineVertices, 0, numOfPrimitives);
                    });
            }

            numOfLines = 0;
        }
        #endregion
    }
}
